package org.papervision3d.core.math.util;
class GLU
{
	public static function makeIdentity(m :Array<Float>) :Void {
		m[0+4*0] = 1; m[0+4*1] = 0; m[0+4*2] = 0; m[0+4*3] = 0;
		m[1+4*0] = 0; m[1+4*1] = 1; m[1+4*2] = 0; m[1+4*3] = 0;
		m[2+4*0] = 0; m[2+4*1] = 0; m[2+4*2] = 1; m[2+4*3] = 0;
		m[3+4*0] = 0; m[3+4*1] = 0; m[3+4*2] = 0; m[3+4*3] = 1;
	}
	
	public static function multMatrices(a:Array<Float>, b:Array<Float>, r:Array<Float>):Void {
		var i :Int = 0, j :Int = 0;
		while (i < 4) {
			while (j < 4) {
				r[Std.int(i*4+j)] = 
				a[Std.int(i * 4 + 0)] * b[Std.int(0 * 4 + j)] +
				a[Std.int(i * 4 + 1)] * b[Std.int(1 * 4 + j)] +
				a[Std.int(i * 4 + 2)] * b[Std.int(2 * 4 + j)] +
				a[Std.int(i * 4 + 3)] * b[Std.int(3 * 4 + j)];
				
				j++;
			}
			
			i++;
		}
	}
	
	public static function multMatrixVec(matrix :Array<Float>, a :Array<Float>, out :Array<Float>) :Void {
		var i :Int = 0;
		while (i < 4) {
			out[i] = 
				a[0] * matrix[Std.int(0 * 4 + i)] + 
				a[1] * matrix[Std.int(1 * 4 + i)] +
				a[2] * matrix[Std.int(2 * 4 + i)] +
				a[3] * matrix[Std.int(3 * 4 + i)];
			i++;
		}
	}
	
	public static function invertMatrix(src :Array<Float>, inverse :Array<Float>):Bool {
		var i :Int = 0, j :Int = 0, k :Int = 0, swap :Int = 0;
		var t :Float = 0;
		var temp :Array<Dynamic> = [];

		while (i < 4) { 
			temp[i] = [];
			
			while (j < 4) { 
				temp[i][j] = src[i * 4 + j];
				j++;
			}
			i++;
		}
		makeIdentity(inverse);
	
		i = 0;
		while (i < 4) {
			/*
			** Look for largest element in column
			*/
			swap = i;
			j = i + 1; 
			while (j < 4) {
				if (Math.abs(temp[j][i]) > Math.abs(temp[i][i])) {
					swap = j;
				}
				j++;
			}
		
			if (swap != i) {
				/*
				** Swap rows.
				*/
				k = 0;
				while (k < 4) {
					t = temp[i][k];
					temp[i][k] = temp[swap][k];
					temp[swap][k] = t;
			
					t = inverse[i*4+k];
					inverse[i*4+k] = inverse[swap*4+k];
					inverse[swap * 4 + k] = t;
					k++;
				}
			}
		
			if (temp[i][i] == 0) {
				/*
				** No non-zero pivot.  The matrix is singular, which shouldn't
				** happen.  This means the user gave us a bad matrix.
				*/
				return false;
			}
		
			t = temp[i][i];
			k = 0;
			while (k < 4) {
				temp[i][k] /= t;
				inverse[i * 4 + k] /= t;
				k++;
			}
			j = 0;
			while (j < 4) {
				if (j != i) {
					t = temp[j][i];
					k = 0;
					while (k < 4) {
						temp[j][k] -= temp[i][k] * t;
						inverse[j * 4 + k] -= inverse[i * 4 + k] * t;
						k++;
					}
				}
				j++;
			}
			
			i++;
		}
		return true;			
	}
	
	public static function ortho(m :Array<Float>, left:Float, right:Float, top:Float, bottom:Float, zNear:Float, zFar:Float) : Bool {
		var tx :Float = (right + left) / (right - left);
		var ty :Float = (top + bottom) / (top - bottom);
		var tz :Float = (zFar+zNear) / (zFar-zNear);
		
		makeIdentity(m);
		
		m[0] = 2 / (right - left);
		m[5] = 2 / (top - bottom);
		m[10] = -2 / (zFar-zNear);
		m[12] = tx;
		m[13] = ty;
		m[14] = tz;

		return true;
	}
	
	public static function perspective(m :Array<Float>, fovy :Float, aspect :Float, zNear :Float, zFar :Float) :Bool {
		var sine :Float, cotangent :Float, deltaZ :Float;
		var radians :Float = (fovy / 2) * (Math.PI / 180);
		
		deltaZ = zFar - zNear;
		sine = Math.sin(radians);
		if ((deltaZ == 0) || (sine == 0) || (aspect == 0)) {
			return false;
		}
		cotangent = Math.cos(radians) / sine;
	
		makeIdentity(m);
		
		m[0] = cotangent / aspect;
		m[5] = cotangent;
		m[10] = -(zFar + zNear) / deltaZ;
		m[11] = -1;
		m[14] = -(2 * zNear * zFar) / deltaZ;
		m[15] = 0;
		
		return true;
	}
	
	public static function scale(m :Array<Float>, sx :Float, sy :Float, sz :Float) :Void
	{
		makeIdentity(m);
		m[0] = sx;
		m[5] = sy;
		m[10] = sz;
	}
	
	public static function unProject(winx:Float, winy:Float, winz:Float, 
										modelMatrix:Array<Float>, projMatrix:Array<Float>, 
										viewport:Array<Dynamic>, out:Array<Float>):Bool {
											
		var finalMatrix :Array<Float> = [];
		var ein :Array<Float> = [];

		multMatrices(modelMatrix, projMatrix, finalMatrix);
		
		if(!invertMatrix(finalMatrix, finalMatrix)) {
			return false;
		}

		ein[0]=winx;
		ein[1]=winy;
		ein[2]=winz;
		ein[3]=1.0;
		
		// Map x and y from window coordinates
		ein[0] = (ein[0] - viewport[0]) / viewport[2];
		ein[1] = (ein[1] - viewport[1]) / viewport[3];
		
		// Map to range -1 to 1
		ein[0] = ein[0] * 2 - 1;
		ein[1] = ein[1] * 2 - 1;
		ein[2] = ein[2] * 2 - 1;
		
		multMatrixVec(finalMatrix, ein, out);
		
		if(out[3] == 0.0) return false;
		out[0] /= out[3];
		out[1] /= out[3];
		out[2] /= out[3];
		
		return true;
	}
}
